#ifndef VIRTQUEUE_H_
#define VIRTQUEUE_H_

/*-
 * Copyright (c) 2011, Bryan Venteicher <bryanv@FreeBSD.org>
 * Copyright (c) 2016 Freescale Semiconductor, Inc.
 * Copyright 2016-2019 NXP
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $FreeBSD$
 */

#include <stdbool.h>
#include <stdint.h>
#include "rpmsg_default_config.h"
typedef uint8_t boolean;

#include "virtio_ring.h"
#include "llist.h"

/* 错误编码 */
#define VQ_ERROR_BASE               (-3000)
#define ERROR_VRING_FULL            (VQ_ERROR_BASE - 1)
#define ERROR_INVLD_DESC_IDX        (VQ_ERROR_BASE - 2)
#define ERROR_EMPTY_RING            (VQ_ERROR_BASE - 3)
#define ERROR_NO_MEM                (VQ_ERROR_BASE - 4)
#define ERROR_VRING_MAX_DESC        (VQ_ERROR_BASE - 5)
#define ERROR_VRING_ALIGN           (VQ_ERROR_BASE - 6)
#define ERROR_VRING_NO_BUFF         (VQ_ERROR_BASE - 7)
#define ERROR_VQUEUE_INVLD_PARAM    (VQ_ERROR_BASE - 8)

#define VQUEUE_SUCCESS              (0)
#define VQUEUE_DEBUG                (false)

/* 这是代替C NULL支持的临时宏. 目前, 所有特定于RTL的函数都在env中. */
#define VQ_NULL                     ((void *)0)

/*
 * 最大virtqueue大小为2^15. 使用该值作为描述符链结束符,
 * 因为它永远不会是描述符表中的有效索引.
 * 这用于验证我们正确地处理了vq_free_cnt.
 */
#define VQ_RING_DESC_CHAIN_END      (32768)
#define VIRTQUEUE_FLAG_INDIRECT     (0x0001U)
#define VIRTQUEUE_FLAG_EVENT_IDX    (0x0002U)
#define VIRTQUEUE_MAX_NAME_SZ       (32) /* 注意对齐 */

/* 支持间接缓冲区描述符. */
#define VIRTIO_RING_F_INDIRECT_DESC (1 << 28)

/* 支持抑制中断，直到达到特定的索引. */
#define VIRTIO_RING_F_EVENT_IDX     (1 << 29)

/* 提示下一个中断应该延迟多长时间. 这只在EVENT_IDX特性被协商时使用. */
typedef enum {
    VQ_POSTPONE_SHORT,
    VQ_POSTPONE_LONG,
    VQ_POSTPONE_EMPTIED /* 直到所有可用的描述符被使用. */
} vq_postpone_t;

/* 本地virtqueue表示, 不在共享内存中. */
struct virtqueue {
    /* 32位对齐 { */
    char vq_name[VIRTQUEUE_MAX_NAME_SZ];
    uint32_t vq_flags;
    int32_t vq_alignment;
    int32_t vq_ring_size;
    void *vq_ring_mem;
    void (*callback_fc)(struct virtqueue *vq);
    void (*notify_fc)(struct virtqueue *vq);
    int32_t vq_max_indirect_size;
    int32_t vq_indirect_mem_size;
    struct vring vq_ring;
    /* } 32位对齐 */

    /* 16位对齐 { */
    uint16_t vq_queue_index;
    uint16_t vq_nentries;
    uint16_t vq_free_cnt;
    uint16_t vq_queued_cnt;

    /* 描述符链表中空闲链表的头. 如果没有空闲描述符, 则将其设置为VQ_RING_DESC_CHAIN_END. */
    uint16_t vq_desc_head_idx;

    /* 使用描述符链表中最后消息的描述符, 尾随vq_ring.used->idx. */
    uint16_t vq_used_cons_idx;

    /* 可用描述符链接中最后消耗的描述符 - 由消费者端使用. */
    uint16_t vq_available_idx;
    /* } 16位对齐 */

    boolean avail_read;  /* 8位宽 */
    boolean avail_write; /* 8位宽 */
    boolean used_read;   /* 8位宽 */
    boolean used_write;  /* 8位宽 */

    uint16_t padd; /* 在此之后32位对齐. */

    void *priv; /* 私有指针, 指向实例指针. */
#if defined(RL_USE_ENVIRONMENT_CONTEXT) && (RL_USE_ENVIRONMENT_CONTEXT == 1)
    void *env; /* 指向环境层内部上下文的私有指针. */
#endif
};

/* 用来保存vring特定信息的结构. */
struct vring_alloc_info {
    void *phy_addr;
    uint32_t align;
    uint16_t num_descs;
    uint16_t pad;
};

#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
struct vq_static_context {
    struct virtqueue vq;
};
#endif

typedef void vq_callback(struct virtqueue *vq);
typedef void vq_notify(struct virtqueue *vq);

#if (VQUEUE_DEBUG == true)
#define VQASSERT_BOOL(_vq, _exp, _msg) \
    do { \
        if (!(_exp)) { \
            env_print("%s: %s - "(_msg), __func__, (_vq)->vq_name); \
            while (1) {}; \
        } \
    } while (0)
#define VQASSERT(_vq, _exp, _msg) \
    VQASSERT_BOOL(_vq, (_exp) != 0, _msg)

#define VQ_RING_ASSERT_VALID_IDX(_vq, _idx) \
    VQASSERT((_vq), (_idx) < (_vq)->vq_nentries, "invalid ring index")

#define VQ_PARAM_CHK(condition, status_var, status_err) \
    if ((status_var == 0) && (condition)) { \
        status_var = status_err; \
    }

#define VQUEUE_BUSY(vq, dir) \
    if ((vq)->dir == false) { \
        (vq)->dir = true; \
    } else { \
        VQASSERT(vq, (vq)->dir == false, "VirtQueue already in use") \
    }

#define VQUEUE_IDLE(vq, dir) ((vq)->dir = false)

#else

#define KASSERT(cond, str)
#define VQASSERT(_vq, _exp, _msg)
#define VQ_RING_ASSERT_VALID_IDX(_vq, _idx)
#define VQ_PARAM_CHK(condition, status_var, status_err)
#define VQUEUE_BUSY(vq, dir)
#define VQUEUE_IDLE(vq, dir)

#endif

#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
int32_t virtqueue_create_static(uint16_t id, const char *name,
                                struct vring_alloc_info *ring,
                                void (*callback_fc)(struct virtqueue *vq),
                                void (*notify_fc)(struct virtqueue *vq),
                                struct virtqueue **v_queue,
                                struct vq_static_context *vq_ctxt);
#else
int32_t virtqueue_create(uint16_t id, const char *name,
                         struct vring_alloc_info *ring,
                         void (*callback_fc)(struct virtqueue *vq),
                         void (*notify_fc)(struct virtqueue *vq),
                         struct virtqueue **v_queue);
#endif

int32_t virtqueue_add_buffer(struct virtqueue *vq, uint16_t head_idx);
int32_t virtqueue_fill_used_buffers(struct virtqueue *vq,
                                    void *buffer, uint32_t len);
int32_t virtqueue_fill_avail_buffers(struct virtqueue *vq,
                                     void *buffer, uint32_t len);
void *virtqueue_get_buffer(struct virtqueue *vq, uint32_t *len, uint16_t *idx);
void *virtqueue_get_available_buffer(struct virtqueue *vq,
                                     uint16_t *avail_idx, uint32_t *len);
int32_t virtqueue_add_consumed_buffer(struct virtqueue *vq,
                                      uint16_t head_idx, uint32_t len);
void virtqueue_disable_cb(struct virtqueue *vq);
int32_t virtqueue_enable_cb(struct virtqueue *vq);
void virtqueue_kick(struct virtqueue *vq);

#if defined(RL_USE_STATIC_API) && (RL_USE_STATIC_API == 1)
void virtqueue_free_static(struct virtqueue *vq);
#else
void virtqueue_free(struct virtqueue *vq);
#endif

void virtqueue_dump(struct virtqueue *vq);
void virtqueue_notification(struct virtqueue *vq);
uint32_t virtqueue_get_desc_size(struct virtqueue *vq);
uint32_t virtqueue_get_buffer_length(struct virtqueue *vq, uint16_t idx);
void vq_ring_init(struct virtqueue *vq);

#endif /* VIRTQUEUE_H_ */
